package com.particles 
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	/**
	 * Simple Particle Manager
	 * @author SChabanov
	 */
	public class ParticleManager
	{
		
		private var _pool:Vector.<Particle>
		
		private var _particles:Vector.<Particle>
		private var _holder:DisplayObjectContainer;
				
		private var _particlesMax:int = 20;
		
		private var _container:Sprite;
		
		private var _source:Bitmap;
		private var _output:BitmapData;
		private var _drawToBitmap:Boolean;
			
		public static const PARTICLES_RENDER_COMPLETE:String = "renderComplete";
						
		public var DROP_PARTICLE:Function;
		public var RANDOM_PARTICLE:Function;
		public var TAIL_PARTICLE:Function;
		public var SNAKE_PARTICLE:Function;
					
		private var _useManualUpdate:Boolean
		private var _globalEnterFrameState:Boolean
		
		private var _customBehavior:Function
		
		
		public function ParticleManager(holder:DisplayObjectContainer,drawToBitmap:Boolean =false) 
		{
			initialize();
			_drawToBitmap = drawToBitmap;
			_holder = holder;
			
			_container = new Sprite();
			_container.scrollRect = new Rectangle(0, 0, _holder.width, _holder.height);
			//_container.cacheAsBitmap = true;
			_container.mouseChildren = false;
			_container.mouseEnabled = false;
			
			if (!_holder.contains(_container) && !drawToBitmap) {
				_holder.addChild(_container);
			}else if(!_source){
				_output = new BitmapData(_holder.width, _holder.height, true, 0x00000000);
				_output.draw(_container);
				_source = new Bitmap(_output);
				//_source.cacheAsBitmap = true;
				_holder.addChild(_source)
			}
						
			_customBehavior = defaultBahavior;
			
			_pool = new Vector.<Particle>();
			_particles = new Vector.<Particle>();
		}
		
		private function initialize():void 
		{
			
			
			DROP_PARTICLE = function():ParticleParams {return ParticleParams.create(randRange( -4, 4),
																			randRange( -2, 1),
																			.99,
																			randRange( -5, 5),
																			-0.2, 
																			60, 
																			0,
																			0.05,1.01,0.3);
													  };
		   RANDOM_PARTICLE = function():ParticleParams {
															return ParticleParams.create(randRange( -10, 10),
															randRange( -10, 10),
															randRange( 0.9, 1),
															randRange( -5, 5),
															randRange( -.9, 0.9),
															randRange(30, 60),
															0,
															randRange(0.02, 0.05),
															randRange(.5, 1),
															randRange(.3, .3));
														 }
		  TAIL_PARTICLE = function():ParticleParams {
														return ParticleParams.create(randRange( -10, 10),
														0, 0.9, randRange( -5, 10), -0.1, 90, 0, 0.02, 1, 0.2);
													}
													
		  SNAKE_PARTICLE = function():ParticleParams {
														return ParticleParams.create(randRange( -8, 8), 0,1,
														randRange( -5, 5), 0,30,1,
														randRange(0.01, 0.02), 1.02, randRange(0.3, 1),
														randRange(45 * Math.PI / 180, 180 * Math.PI / 180), randRange(-0.01, 0.02));
													 }
		}
						
		// returns a random value between min and max
		public function randRange(min:Number, max:Number):Number
		{
			return Math.random() * (max - min) + min;
		}				
		
		public function clear():void
		{
			_container.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
			if (_container.parent)
			{
				_holder.removeChild(_container);
				
			}
			_particles = null;
			_pool = null;
			if (_drawToBitmap)
			{
				_holder.removeChild(_source);
				_output.dispose();
				_output = null;
			}
		}
		
		public function createParticle(classItem:Class,params:ParticleParams,emiter:Point, filters:Array = null):Particle {
			
			if (_particles.length == _particlesMax) return null;
			
			_container.filters = filters;
			
			var p:Particle;
			if (_pool != null && _pool.length > 0) {
				p = _pool.pop();
				p.clip.x = emiter.x;
				p.clip.y = emiter.y;
			}else {
				classItem = classItem == null ? DefaultParticle : classItem;
				p = new Particle(classItem, emiter.x, emiter.y);
			}
			
			p.angleSin = params.angleSin
			p.angleCos = params.angleCos;
			p.angleIncrement = params.angleIncrement; 
			p.xVel = params.xVelosicty;
			p.yVel = params.yVelosicty;
			p.rotation = params.rotation
			p.clip.scaleX = p.clip.scaleY = params.scale;
			p.drag = params.drag;
			p.gravity = params.gravity;
			p.fade = params.fade;
			p.shrink = params.shrink;
			p.lifeTime = params.lifeTime;
			p.holdTime = params.holdtime;
			_particles.push(p);
			
			if (!_container.hasEventListener(Event.ENTER_FRAME) && !_useManualUpdate) _container.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
			return p;
		}
		
		public function update():void
		{
			if (_useManualUpdate && _globalEnterFrameState)
			{
				onEnterFrame(null);
			}
		}
		
		private function onEnterFrame(e:Event):void 
		{
			
			for (var i:int = 0; i < _particles.length; i++) 
			{
				if (_particles[i].holdTime == 0) {
					simpleBehavior(_particles[i]);		
					checkToDestroy(_particles[i]);
				}else {
					_particles[i].holdTime--;
				}
			}
			if (_drawToBitmap)
			{
				_output.fillRect(_output.rect, 0);
				_output.draw(_container);
			}
		}
		
		private function checkToDestroy(p:Particle):void 
		{
			if (p.lifeTime <= 0) {
				_pool.push(_particles.splice(_particles.indexOf(p), 1)[0]);
				_container.removeChild(p.clip);
				
			}
			if (_particles.length == 0) {
				 _container.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
				 _pool.length = 0;
				 _globalEnterFrameState = false;
				  _holder.dispatchEvent(new Event(PARTICLES_RENDER_COMPLETE));
			}
			
		}
		
		private function simpleBehavior(p:Particle):void {
			// add the velocity to the clip's position
			
			if (!p.clip.parent)_container.addChild(p.clip);
			
			var sin:Number = isNaN(p.angleSin) ? 0 : Math.sin(p.angleSin);
			var cos:Number = isNaN(p.angleCos) ? 0 : Math.cos(p.angleCos);
			
			p.clip.x += p.xVel + cos; 
			p.clip.y += p.yVel + sin; 
			
			p.clip.rotation += p.rotation; 			
			// apply drag
			
			p.xVel *= p.drag; 
			p.yVel *= p.drag; 
			
			p.yVel += p.gravity; 
			
			p.clip.scaleX *= p.shrink;
			p.clip.scaleY *= p.shrink; 
			
			if (!isNaN(p.angleSin)) p.angleSin += p.angleIncrement;
			if (!isNaN(p.angleCos)) p.angleCos += p.angleIncrement;
									
			_customBehavior(p)
			
			p.lifeTime--;
		
		}
		
		private function defaultBahavior(p:Particle):void 
		{
			p.clip.alpha -= p.fade;
		}
				
		public function get particlesMax():int 
		{
			return _particlesMax; 
		}
		
		public function set particlesMax(value:int):void 
		{
			_particlesMax = value;
		}
		
		public function set customBehavior(value:Function):void 
		{
			if (value == null) return;
			_customBehavior = value;
		}
		
		public function get useManualUpdate():Boolean 
		{
			return _useManualUpdate;
		}
		
		public function set useManualUpdate(value:Boolean):void 
		{
			_useManualUpdate = value;
		}
	}
}
import flash.display.Shape;

class DefaultParticle extends Shape
{
	
	
	public function DefaultParticle()
	{
		super()
		
		graphics.beginFill(0xFF0000, 1);
		graphics.drawCircle(0,0, 10);
		graphics.endFill();
	}
	

}
